---
title: "Procedures"
group: "Getting Started"
groupOrder: 2
---

Procedures allow you to add additional context to a set of server actions, such as the `userId` of the caller. They are useful for ensuring certain actions can only be called if specific conditions are met, like the user being logged in or having certain permissions.

## Creating a basic procedure

Here is an example of a simple procedure that ensures a user is logged in, and passes that information to the ctx of the handler

```ts title="actions.ts"
"use server"
import { createServerActionProcedure } from "zsa"


const authedProcedure = createServerActionProcedure() // [!code highlight]
  .handler(async () => {
    try {
      const { email, id } = await getUser();

      return {
        user: {
          email,
          id,
        }
      }
    } catch {
      throw new Error("User not authenticated")
    }
  })

export const updateEmail = authedProcedure // [!code highlight]
  .createServerAction()  // [!code highlight]
  .input(z.object({
    newEmail: z.string()
  })).handler(async ({input, ctx}) => {
    const {user} = ctx

    // Update user's email in the database
    await db.update(users).set({
      email: newEmail,
    }).where(eq(users.id, user.id))

    return input.newEmail
  })
```

In this example:

1. We create a `authedProcedure` that verifies the user is logged in by calling `getUser()`.
2. If successful, it returns the user's email and id. If not, it throws an error.
3. We create an `updateEmail` by chaining `createServerAction()` off the procedure. Invoking this action will now call the procedure's handler before running its own handler, thus ensuring that only authed users can use this server action.

## Chaining procedures

What if you need to restrict an action based on additional conditions, like the user being an admin? You can create an chained procedure that to run a procedure AFTER another procedure and feed forward that `ctx`.

```ts title="actions.ts"
const isAdminProcedure = createServerActionProcedure(authedProcedure) // [!code highlight]
  .handler(async ({ ctx }) => {
    const role = getUserRole(ctx.user.id)

    if (role !== "admin") {
      throw new Error("User is not an admin")
    }

    return {
      user: {
          id: ctx.user.id,
          email: ctx.user.email,
          role: role
      }
    }
});

const deleteUser = isAdminProcedure // [!code highlight]
  .createServerAction()
  .input(z.object({
    userIdToDelete: z.string()
  })).handler(async ({ input, ctx }) => {
    const { userIdToDelete } = input
    const { user } = ctx // receive the context from the procedures

    // Delete user from database
    await db.delete(users).where(eq(users.id, userIdToDelete));

    return userIdToDelete;
});

```

In this example:

1. We call `createServerActionProcedure` and chain off of `authedProcedure` by passing it as an argument.
2. We use the `ctx` from the `authedProcedure`'s handler and further determine if the user is an admin.
3. We create a `deleteUser` action that runs the two procedures, in order before the actions handler and also has access to the `isAdminProcedure`'s return value in the `ctx`. The action will now only run it's handler if the user is an admin and the inital procedure's don't throw an error.

## Procedures with input

You can also create procedures that require certain input, such as requiring a `postId`, and validate that the user has access to that resource:

```ts title="actions.ts"
const ownsPostProcedure = createServerActionProcedure(isAdminProcedure) // [!code highlight]
    .input(
        z.object({ postId: z.string() })
    )
    .handler(async ({ input, ctx }) => {

        //validate post ownership
        const ownsPost = await checkUserOwnsPost(ctx.user.id, input.postId)

        if (!ownsPost) {
            throw new Error("UNAUTHORIZED")
        }

        return {
            user: ctx.user,
            post: {
                id: input.postId,
            },
        }
})
```

Actions using this `ownsPostProcedure` will now always require a `postId` in the `input`. It will now validate the user's ownership before executing the action's handler:

```ts title="actions.ts"
const updatePostName = ownsPostProcedure // [!code highlight]
  .createServerAction()
  .input(z.object({ newPostName: z.string() }))
  .handler(async ({ input, ctx }) => {

    console.log({
      // input contains postId and newPostName
      newPostName: input.newPostName, // [!code highlight]
      postId: input.postId, // [!code highlight]
      // ctx contains user and post returned by procedures
      user: ctx.user, // [!code highlight]
      post: ctx.post, // [!code highlight]
    })

    return "GREAT SUCCESS"
  })
```

Now, when we call the `updatePostName`, both `postId` and `newPostName` will be required in the input.

```ts
const [data, err] = await updatePostName({
  newPostName: "hello world",
  postId: "post_id_123", // [!code highlight]
})
```

Chaining procedures is a powerful way to pass context into your actions and ensure that certain conditions are met before running the action's handler.
